#pragma once
#include <cstdarg>
#include <sfse_common/Utilities.h>

#include "Addresses.h"
#include "ScaleformGFxTypes.h"
#include "sfse_common/Types.h"

namespace Scaleform
{
    namespace GFx
    {
        class Movie;

        class Value
        {
        public:
            Value() : objectInterface(NULL), type(kType_Undefined), unk18(nullptr) { data.obj = nullptr; }
            Value(u32 v) : objectInterface(NULL), type(kType_UInt), unk18(nullptr) { data.u32 = v; }
            Value(s32 v) : objectInterface(NULL), type(kType_Int), unk18(nullptr) { data.s32 = v; }
            Value(double v) : objectInterface(NULL), type(kType_Number), unk18(nullptr) { data.number = v; }
            Value(bool v) : objectInterface(NULL), type(kType_Bool), unk18(nullptr) { data.boolean = v; }
            Value(const char* ps) : objectInterface(NULL), type(kType_String), unk18(nullptr) { data.string = ps; }

            Value(Value* value) : objectInterface(value->objectInterface), type(value->type), unk18(value->unk18)
            {
                data.obj = value->data.obj;
            }

            ~Value();

            enum Type
            {
                kType_Undefined = 0,
                kType_Null,
                kType_Bool,
                kType_Int,
                kType_UInt,
                kType_Number,
                kType_String,
                kType_Unknown7,
                kType_Object,
                kType_Array,
                kType_DisplayObject,
                kType_Function,

                kTypeFlag_Managed = 1 << 6,

                kMask_Type = 0x8F,
            };

            /*enum ValueType {
                VT_Undefined = 0x00,
                VT_Null = 0x01,
                VT_Boolean = 0x02,
                VT_Int = 0x03,
                VT_UInt = 0x04,
                VT_Number = 0x05,
                VT_String = 0x06,
                VT_StringW = 0x07,
                VT_Object = 0x08,
                VT_Array = 0x09,
                VT_DisplayObject = 0x0a,
                VT_Closure = 0x0b,
                VT_ConvertBoolean = VTC_ConvertBit|VT_Boolean,
                VT_ConvertInt = VTC_ConvertBit|VT_Int,
                VT_ConvertUInt = VTC_ConvertBit|VT_UInt,
                VT_ConvertNumber = VTC_ConvertBit|VT_Number,
                VT_ConvertString = VTC_ConvertBit|VT_String,
                VT_ConvertStringW = VTC_ConvertBit|VT_StringW
            };*/

            union Data
            {
                u32 u32;
                s32 s32;
                double number;
                bool boolean;
                const char* string;
                const char** managedString;
                void* obj;
            };


            class DisplayInfo
            {
            public:
                DisplayInfo() : _varsSet(0), unkD0(0), unkD6(0)
                {
                }

                enum
                {
                    kChange_x = (1 << 0),
                    kChange_y = (1 << 1),
                    kChange_rotation = (1 << 2),
                    kChange_xscale = (1 << 3),
                    kChange_yscale = (1 << 4),
                    kChange_alpha = (1 << 5),
                    kChange_visible = (1 << 6),
                    kChange_z = (1 << 7),
                    kChange_xrotation = (1 << 8),
                    kChange_yrotation = (1 << 9),
                    kChange_zscale = (1 << 10),
                    kChange_FOV = (1 << 11),
                    kChange_projMatrix3D = (1 << 12),
                    kChange_viewMatrix3D = (1 << 13)
                };

                /*enum Flags {
                    V_x = 0x01,
                    V_y = 0x02,
                    V_rotation = 0x04,
                    V_xscale = 0x08,
                    V_yscale = 0x10,
                    V_alpha = 0x20,
                    V_visible = 0x40,
                    V_z = 0x80,
                    V_xrotation = 0x100,
                    V_yrotation = 0x200,
                    V_zscale = 0x400,
                    V_FOV = 0x800,
                    V_projMatrix3D = 0x1000,
                    V_viewMatrix3D = 0x2000,
                    V_edgeaaMode = 0x4000
                };*/

                double _x; // 00
                double _y; // 08
                double _rotation; // 10
                double _xScale; // 18
                double _yScale; // 20
                double _alpha; // 28
                bool _visible; // 30
                double _z; // 38
                double _xRotation; // 40
                double _yRotation; // 48
                double _zScale; // 50
                double _perspFOV; // 58
                Render::Matrix3F _viewMatrix3D; // 60
                Render::Matrix4F _perspectiveMatrix3D; // A0
                u32 unkD0; // D0
                u16 _varsSet; // D4
                u16 unkD6; // D6

                void SetX(double x)
                {
                    SetFlags(kChange_x);
                    _x = x;
                }

                void SetY(double y)
                {
                    SetFlags(kChange_y);
                    _y = y;
                }

                void SetRotation(double degrees)
                {
                    SetFlags(kChange_rotation);
                    _rotation = degrees;
                }

                void SetXScale(double xscale)
                {
                    SetFlags(kChange_xscale);
                    _xScale = xscale;
                }

                void SetYScale(double yscale)
                {
                    SetFlags(kChange_yscale);
                    _yScale = yscale;
                }

                void SetAlpha(double alpha)
                {
                    SetFlags(kChange_alpha);
                    _alpha = alpha;
                }

                void SetVisible(bool visible)
                {
                    SetFlags(kChange_visible);
                    _visible = visible;
                }

                void SetZ(double z)
                {
                    SetFlags(kChange_z);
                    _z = z;
                }

                void SetXRotation(double degrees)
                {
                    SetFlags(kChange_xrotation);
                    _xRotation = degrees;
                }

                void SetYRotation(double degrees)
                {
                    SetFlags(kChange_yrotation);
                    _yRotation = degrees;
                }

                void SetZScale(double zscale)
                {
                    SetFlags(kChange_zscale);
                    _zScale = zscale;
                }

                void SetFOV(double fov)
                {
                    SetFlags(kChange_FOV);
                    _perspFOV = fov;
                }

                void SetProjectionMatrix3D(const Render::Matrix4F* pmat)
                {
                    if (pmat)
                    {
                        SetFlags(kChange_projMatrix3D);
                        _perspectiveMatrix3D = *pmat;
                    }
                    else
                        ClearFlags(kChange_projMatrix3D);
                }

                void SetViewMatrix3D(const Render::Matrix3F* pmat)
                {
                    if (pmat)
                    {
                        SetFlags(kChange_viewMatrix3D);
                        _viewMatrix3D = *pmat;
                    }
                    else
                        ClearFlags(kChange_viewMatrix3D);
                }

                // Convenience functions
                void SetPosition(double x, double y)
                {
                    SetFlags(kChange_x | kChange_y);
                    _x = x;
                    _y = y;
                }

                void SetScale(double xscale, double yscale)
                {
                    SetFlags(kChange_xscale | kChange_yscale);
                    _xScale = xscale;
                    _yScale = yscale;
                }

                void SetFlags(u32 flags) { _varsSet |= flags; }
                void ClearFlags(u32 flags) { _varsSet &= ~flags; }
            };

            class ObjectInterface
            {
            public:
                class ArrayVisitor
                {
                public:
                    virtual ~ArrayVisitor()
                    {
                    }

                    virtual void Visit(u32 idx, Value* val) = 0;
                };

                class ObjVisitor
                {
                public:
                    virtual ~ObjVisitor()
                    {
                    }

                    virtual bool ShowDisplayMembers(void) { return false; }
                    virtual void Visit(const char* member, Value* value) = 0;
                };

                void* unk00;
                Movie* view;

                MEMBER_FN_PREFIX(ObjectInterface);
                DEFINE_MEMBER_FN(HasMember, bool, ObjectInterface_HasMember_ADDRESS, void* pData, const char* name);
                DEFINE_MEMBER_FN(GetMember, bool, ObjectInterface_GetMember_ADDRESS, void* pData, const char* name,
                                 Value* pValue, bool isDisplayObj);
                DEFINE_MEMBER_FN(SetMember, bool, ObjectInterface_SetMember_ADDRESS, void* pData, const char* name,
                                 const Value* pValue, bool isDisplayObj);
                DEFINE_MEMBER_FN(Invoke, bool, ObjectInterface_Invoke_ADDRESS, void * pData, Value * result,
                                 const char * name, Value * args, u64 numArgs, u8 isDisplayObj);
                DEFINE_MEMBER_FN(CreateEmptyMovieClip, bool, ObjectInterface_CreateEmptyMovieClip_ADDRESS, void* pData,
                                 Value* pValue, const char* instanceName, s32 depth);
                DEFINE_MEMBER_FN(AttachMovie, bool, ObjectInterface_AttachMovie_ADDRESS, void* pData, Value* pValue,
                                 const char* symbolName, const char* instanceName, s32 depth, const void * initArgs);
                DEFINE_MEMBER_FN(GetArraySize, u32, ObjectInterface_GetArraySize_ADDRESS, void * pData);

                DEFINE_MEMBER_FN(SetArraySize, bool, ObjectInterface_SetArraySize_ADDRESS, void * pData, u32 size);
                DEFINE_MEMBER_FN(GetElement, bool, ObjectInterface_GetElement_ADDRESS, void * pData, u32 index, Value * value);

                DEFINE_MEMBER_FN(SetElement, bool, ObjectInterface_SetElement_ADDRESS, void * pData, u32 index, const Value& value); //
                DEFINE_MEMBER_FN(RemoveElements, bool, ObjectInterface_RemoveElements_ADDRESS, void * pData, u32 index, s32 count); //
                
                DEFINE_MEMBER_FN(PushBack, bool, ObjectInterface_PushBack_ADDRESS, void * pData, const Value& value);
                DEFINE_MEMBER_FN(PopBack, bool, ObjectInterface_PopBack_ADDRESS, void * pData, Value * value);
                DEFINE_MEMBER_FN(VisitElements, void, ObjectInterface_VisitElements_ADDRESS, void * pData, ArrayVisitor * visitor, u32 idx,
                                 s32 count);
                DEFINE_MEMBER_FN(GotoAndPlay, bool, ObjectInterface_GotoAndPlay_ADDRESS, void * pData, u32 frameNumber, bool stop);
                DEFINE_MEMBER_FN(GotoAndPlayL, bool, ObjectInterface_GotoAndPlayL_ADDRESS, void * pData, const char * frameLabel, bool stop);

                DEFINE_MEMBER_FN(VisitMembers, void, ObjectInterface_VisitMembers_ADDRESS, void * pData, ObjVisitor * visitor, bool isDisplayObj);
                DEFINE_MEMBER_FN(GetText, bool, ObjectInterface_GetText_ADDRESS, void * pData, Value * value, bool html);
                DEFINE_MEMBER_FN(SetText, bool, ObjectInterface_SetText_ADDRESS, void * pData, const char * text, bool html);

                DEFINE_MEMBER_FN(SetTextW, bool, ObjectInterface_SetTextW_ADDRESS, void * pData, const wchar_t * text, bool html); //
                DEFINE_MEMBER_FN(GetParent, bool, ObjectInterface_GetParent_ADDRESS, void * pData, Value * value); // 
                
                DEFINE_MEMBER_FN(GetDisplayInfo, bool, 0x0, void * pData, DisplayInfo * displayInfo);
                DEFINE_MEMBER_FN(SetDisplayInfo, bool, 0x0, void * pData, DisplayInfo * displayInfo);
                DEFINE_MEMBER_FN(ObjectAddRef, void, ObjectInterface_ObjectAddRef_ADDRESS, Value * value, void * pData);
                // AddManaged_Internal
                DEFINE_MEMBER_FN(ObjectRelease, void, ObjectInterface_ObjectRelease_ADDRESS, Value * value,
                                 void * pData); // ReleaseManaged_Internal
            };

            void* unk00;
            void* unk08;
            ObjectInterface* objectInterface; // 10
            Type type; // 18
            Data data; // 20
            void* unk18; // 28

            u32 GetType(void) const { return type & kMask_Type; }
            bool IsManaged(void) const { return (type & kTypeFlag_Managed) != 0; }
            void CleanManaged(void);
            void AddManaged(void);

            bool IsUndefined() const { return GetType() == kType_Undefined; }
            bool IsNull() const { return GetType() == kType_Null; }
            bool IsBool() const { return GetType() == kType_Bool; }
            bool IsNumber() const { return GetType() == kType_Number; }
            bool IsString() const { return GetType() == kType_String; }

            bool IsObject() const
            {
                return (GetType() == kType_Object) || GetType() == kType_Array || GetType() == kType_DisplayObject;
            }

            bool IsArray() const { return GetType() == kType_Array; }
            bool IsDisplayObject() const { return GetType() == kType_DisplayObject; }
            bool IsFunction() const { return GetType() == kType_Function; }

            void SetUndefined(void);
            void SetNull(void);
            void SetBool(bool value);
            void SetInt(s32 value);
            void SetUInt(u32 value);
            void SetNumber(double value);
            void SetString(const char* value);

            bool GetBool(void) const;
            const char* GetString(void) const;
            double GetNumber(void) const;
            s32 GetInt(void) const;
            u32 GetUInt(void) const;

            bool HasMember(const char* name);
            bool SetMember(const char* name, Value* value);
            bool GetMember(const char* name, Value* value);
            bool Invoke(const char* name, Value* result, Value* args, u32 numArgs);

            bool CreateEmptyMovieClip(Value* pValue, const char* instanceName, s32 depth);
            bool AttachMovie(Value* pValue, const char* symbolName, const char* instanceName, s32 depth,
                             const void* initArgs);
            bool GotoLabeledFrame(const char* frameLabel, bool stop);

            u32 GetArraySize();
            bool SetArraySize(u32 size);
            bool GetElement(u32 index, Value* value);
            bool PushBack(const Value& value);
            bool PopBack(Value* value);
            void VisitElements(ObjectInterface::ArrayVisitor* visitor, u32 idx, s32 count);
            void VisitMembers(ObjectInterface::ObjVisitor* visitor);
            bool GetText(Value* value, bool html);
            bool SetText(const char* text, bool html);
            bool GetDisplayInfo(DisplayInfo* displayInfo);
            bool SetDisplayInfo(DisplayInfo* displayInfo);

            MEMBER_FN_PREFIX(Value);
            DEFINE_MEMBER_FN(RemoveChild_Internal, void, 0x0, Value * name);
        };
    };
};
